/**
 * Author(s): xuming.Wong <xuming.Wong@gmail.com>
 * Origin source from MaNGOS(GPL2).
 * Copyright (C) 2005-2010 MaNGOS <http://getmangos.com/>
 */

#include "WorldSession.h"

#include "ObjectManager.h"

namespace PQServiceCore
{
	WorldSession::WorldSession(WorldSocket *socket,uint32 accountId, AccountTypes security, uint8 expansion, time_t muteTime, LocaleConstant locale)
		: _socket(socket),_accountId(accountId),_security(security),_expansion(expansion),_muteTime(muteTime),_groupAutoJoin(false),
		_groupAutoAdd(false),_logoutTime(0),_inQueue(false), _playerLoading(false), _playerLogout(false), _playerRecentlyLogout(false), 
		_playerSave(false),_locale(SWorld.GetAvailableDbcLocale(locale)), _localeIndex(SObjectMgr.getLocaleIndex(locale)),
		_latency(0),_tutorialState(TUTORIALDATA_UNCHANGED)
	{
		if(_socket){
			_address=_socket->GetRemoteAddress();
			_socket->AddReference();
		}
	}

	WorldSession::~WorldSession()
	{
		///- unload player if not unloaded
		if(_player){
			LogoutPlayer(true);//logout and save player data
		}

		/// - If have unclosed socket, close it
		if (_socket){
			_socket->CloseSocket();
			_socket->RemoveReference();
			_socket = 0;
		}

		///- empty incoming packet queue
		WorldPacket* packet;
		while(_recvQueue.next(packet)){
			delete packet;
		}

	}

	void WorldSession::SendPacket(const WorldPacket* packet)
	{
		if (!_socket){
			return;
		}
		if (_socket->SendPacket(*packet) == -1){
			_socket->CloseSocket ();
		}

	}

	void WorldSession::QueuePacket(WorldPacket* packet)
	{
		_recvQueue.add(packet);
	}

	
	/// Update the WorldSession (triggered by World update)
	bool WorldSession::Update(uint32 deltaT)
	{
		///- Retrieve packets from the receive queue and call the appropriate handlers
		/// not proccess packets if socket already closed
		WorldPacket* packet;
		while(_socket&&!_socket->isClosed()&&_recvQueue.next(packet)){
			const OpcodeHandler& opHandle=opcodeTable[packet->GetOpcode()];
			try{
				switch (opHandle.status){
					case STATUS_LOGGEDIN:
						if(!_player){
							// skip STATUS_LOGGEDIN opcode unexpected errors if player logout sometime ago - this can be network lag delayed packets
							if(!_playerRecentlyLogout){
								LogUnexpectedOpcode(packet, "the player has not logged in yet");
							}
						}else if(_player->isInWorld()){
							ExecuteOpcode(opHandle, packet);
						}

						// lag can cause STATUS_LOGGEDIN opcodes to arrive after the player started a transfer
						break;
					case STATUS_LOGGEDIN_OR_RECENTLY_LOGGEDOUT:
						if(!_player && !_playerRecentlyLogout){
							LogUnexpectedOpcode(packet, "the player has not logged in yet and not recently logout");
						}else{
							// not expected _player or must checked in packet hanlder
							ExecuteOpcode(opHandle, packet);
						}
						break;
					case STATUS_TRANSFER:
						if(!_player)
							LogUnexpectedOpcode(packet, "the player has not logged in yet");
						else if(_player->isInWorld())
							LogUnexpectedOpcode(packet, "the player is still in world");
						else
							ExecuteOpcode(opHandle, packet);
						break;
					case STATUS_AUTHED:
						// prevent cheating with skip queue wait
						if(_inQueue){
							LogUnexpectedOpcode(packet, "the player not pass queue yet");
							break;
						}

						// single from authed time opcodes send in to after logout time
						// and before other STATUS_LOGGEDIN_OR_RECENTLY_LOGGOUT opcodes.
						if (packet->GetOpcode() != CMSG_SET_ACTIVE_VOICE_CHANNEL){
							_playerRecentlyLogout = false;
						}

						ExecuteOpcode(opHandle, packet);
						break;
					case STATUS_NEVER:
						SLog.outError( "SESSION: received not allowed opcode %s (0x%.4X)",
							LookupOpcodeName(packet->GetOpcode()),
							packet->GetOpcode());
						break;
					case STATUS_UNHANDLED:
						SLog.debugLog("SESSION: received not handled opcode %s (0x%.4X)",
							LookupOpcodeName(packet->GetOpcode()),
							packet->GetOpcode());
						break;
					default:
						SLog.outError("SESSION: received wrong-status-req opcode %s (0x%.4X)",
							LookupOpcodeName(packet->GetOpcode()),
							packet->GetOpcode());
						break;
				}


			}catch(ByteBufferException&){
				SLog.outError("WorldSession::Update ByteBufferException occured while parsing a packet (opcode: %u) from client %s, accountid=%i.",packet->GetOpcode(), GetRemoteAddress().c_str(), GetAccountId());
				if (SLog.HasLogLevelOrHigher(LOG_LVL_DEBUG)){
					SLog.outDebug("Dumping error causing packet:");
					packet->hexlike();
				}

				if (SWorld.getConfig(CONFIG_BOOL_KICK_PLAYER_ON_BAD_PACKET)){
					SLog.debugLog("Disconnecting session [account id %u / address %s] for badly formatted packet.",GetAccountId(), GetRemoteAddress().c_str());
					KickPlayer();
				}
			}
			delete packet;
		}

		///- Cleanup socket pointer if need
		if (_socket && _socket->isClosed()){
			_socket->RemoveReference();
			_socket = 0;
		}

		///- If necessary, log the player out
		time_t currTime = time(NULL);
		if (!_socket || (ShouldLogOut(currTime) && !_playerLoading)){
			LogoutPlayer(true);
		}

		if (!_socket){
			return false; //Will remove this session from the world session map
		}

		return true;
	}

	/// Logging helper for unexpected opcodes
	void WorldSession::LogUnexpectedOpcode(WorldPacket* packet, const char *reason)
	{
		SLog.outError( "SESSION: received unexpected opcode %s (0x%.4X) %s",
			LookupOpcodeName(packet->GetOpcode()),
			packet->GetOpcode(),
			reason);
	}

	void WorldSession::ExecuteOpcode(const OpcodeHandler& opHandle, WorldPacket* packet )
	{
		// need prevent do internal far teleports in handlers because some handlers do lot steps
		// or call code that can do far teleports in some conditions unexpectedly for generic way work code
		if(_player){
			_player->SetCanDelayTeleport(true);
		}

		(this->*opHandle.handler)(*packet);

		if(_player){
			// can be not set in fact for login opcode, but this not create porblems.
			_player->SetCanDelayTeleport(false);

			//we should execute delayed teleports only for alive(!) players
			//because we don't want player's ghost teleported from graveyard
			if (_player->IsHasDelayedTeleport()){
				_player->Teleport();
			}

		}

		if(packet->rpos()<packet->wpos()&&SLog.HasLogLevelOrHigher(LOG_LVL_DEBUG)){
			LogUnprocessedTail(packet);
		}
	}


	/// Logging helper for unexpected opcodes
	void WorldSession::LogUnprocessedTail(WorldPacket *packet)
	{
		SLog.outError( "SESSION: opcode %s (0x%.4X) have unprocessed tail data (read stop at " SIZEFMTD " from " SIZEFMTD ")",
			LookupOpcodeName(packet->GetOpcode()),
			packet->GetOpcode(),
			packet->rpos(),packet->wpos());
	}



}
